package egar

import (
	"math"

	"github.com/gorilla/websocket"
)

type PlayerState int

const (
	PlayerStateReady PlayerState = iota
	PlayerStatePlaying
	PlayerStateDead
	PlayerStateDisconnect
)

type Player struct {
	State            PlayerState
	Name             string
	Color            uint32
	Cells            Set
	AliveCellTotal   int
	VisibleBalls     []*Ball
	TotalMass        float64
	TargetX, TargetY float64 // mouse position
	LastVomitTime    int64   // millisecond
	LastSplitTime    int64   // millisecond
	Vomiting         bool
	Spliting         bool
	ViewScale        float64
	ViewCenterX      float64
	ViewCenterY      float64
	ViewWidth        float64
	ViewHeight       float64
	packetHandler    *packetHandler
	protocol         *protocol
	game             *Game
	Connection       *websocket.Conn
}

func NewPlayer(game *Game, Connection *websocket.Conn) *Player {
	p := &Player{
		game:       game,
		Connection: Connection,
	}
	p.packetHandler = newPacketHandler(p)
	p.protocol = newProtocol(p)
	p.Cells = NewThreadUnsafeSet()
	p.Color = RandomColor()
	return p
}

func (p *Player) updateViewScale() {
	p.ViewScale = 10.0
}

func (p *Player) updateViewCenter() {
	if p.Cells.Len() == 1 {
		p.Cells.ForEach(func(value interface{}) bool {
			if cell, ok := value.(*Ball); ok {
				p.ViewCenterX = cell.X
				p.ViewCenterY = cell.Y
				return false
			}
			return true
		})
		return
	}
	avgX := 0.0
	avgY := 0.0
	p.Cells.ForEach(func(value interface{}) bool {
		if cell, ok := value.(*Ball); ok {
			avgX += cell.X
			avgY += cell.Y
		}
		return true
	})
	avgX /= float64(p.Cells.Len())
	avgY /= float64(p.Cells.Len())
	p.ViewCenterX = (p.ViewCenterX*2 + avgX) / 3
	p.ViewCenterY = (p.ViewCenterY*2 + avgY) / 3
}

func (p *Player) updateViewSize() {
	p.ViewWidth = 200
	p.ViewHeight = 100
}

func (p *Player) updateVisibleBalls() {
	p.VisibleBalls = make([]*Ball, 0)
	p.VisibleBalls = append(p.VisibleBalls, p.game.gameMap.balls...)
}

func (p *Player) update(currentTime int64, deltaTime float64) {
	if p.Vomiting {
		p.Vomit(currentTime)
	}

	if p.Spliting {
		p.Spliting = false
		p.Split(currentTime)
	}

	p.TotalMass = 0

	p.Cells.ForEach(func(value interface{}) bool {
		if cell, ok := value.(*Ball); ok && !cell.Removed {
			p.TotalMass += cell.Mass
		}
		return true
	})

	p.updateViewScale()
	p.updateViewCenter()
	p.updateViewSize()
	p.updateVisibleBalls()
}

func (p *Player) Vomit(currentTime int64) {
	if currentTime-p.LastVomitTime < int64(p.game.config.CellVomitInterval) {
		return
	}
	p.LastVomitTime = currentTime
	p.Cells.ForEach(func(item interface{}) bool {
		cell, ok := item.(*Ball)
		if !ok || cell.Removed || cell.Mass < p.game.config.CellMinMassToVomit {
			return true
		}
		cell.AddMass(-p.game.config.SporeMass)
		dx := math.Cos(cell.Angle)
		dy := math.Sin(cell.Angle)
		spore := NewSporeBall(
			cell.X+dx*(cell.Radius+6),
			cell.Y+dy*(cell.Radius+6),
			p.game.config.SporeMass,
			cell.Color,
		)
		spore.setBoost(dx*64, dy*64)
		p.game.gameMap.hatchBall(spore)
		return true
	})
}

func (p *Player) Split(currentTime int64) {
	if currentTime-p.LastSplitTime < int64(p.game.config.CellSplitInterval) || p.Cells.Len() >= p.game.config.PlayerMaxCells {
		return
	}
	var newCells []*Ball
	p.Cells.ForEach(func(item interface{}) bool {
		if len(newCells)+p.Cells.Len() >= p.game.config.PlayerMaxCells {
			return false
		}
		cell, ok := item.(*Ball)
		if !ok || cell.Removed || cell.Mass < p.game.config.CellMinMassToSplit {
			return true
		}
		newMass := cell.Mass / 2
		cell.SetMass(newMass)
		dx := math.Cos(cell.Angle)
		dy := math.Sin(cell.Angle)
		newCell := NewCellBall(
			cell.X+dx*cell.Radius,
			cell.Y+dy*cell.Radius,
			cell.Color,
			newMass,
			p,
		)
		newCell.setBoost(
			dx*(32+(newCell.Radius*2.5)),
			dy*(32+(newCell.Radius*2.5)),
		)
		newCells = append(newCells, newCell)
		return true
	})
	if len(newCells) > 0 {
		p.LastSplitTime = currentTime
		for _, newCell := range newCells {
			p.game.gameMap.hatchBall(newCell)
		}
	}
}
